-- ThresholdMonitor.lua
-- Compact overlay + debug panel for live thresholds (event-driven, throttled)

local ADDON, DFFT = ...
-- ✅ IMPORTANT: do NOT create a DB fallback; if Core isn't ready, bail out.
if not DFFT or not DFFT.DB then return end
local DB = DFFT.DB

DFFT.Monitor = DFFT.Monitor or {}
local M = DFFT.Monitor

-- -----------------------------
-- Defaults & small utils
-- -----------------------------
-- ⚠️ Never assume subtables exist; guarantee them via this function.
local function ensureMonitorDB()
  DB.monitor = DB.monitor or {}
  local mon = DB.monitor
  mon.overlay = mon.overlay or { enabled = false, x = 160, y = -140, scale = 1.0, alpha = 1.0, locked = false, bg = true }
  mon.debug   = mon.debug   or { enabled = false, w = 380, h = 280 }
  -- normalize missing keys (in case of partial wipe/older versions)
  if mon.overlay.enabled == nil then mon.overlay.enabled = false end
  if mon.overlay.x == nil then mon.overlay.x = 160 end
  if mon.overlay.y == nil then mon.overlay.y = -140 end
  if mon.overlay.scale == nil then mon.overlay.scale = 1.0 end
  if mon.overlay.alpha == nil then mon.overlay.alpha = 1.0 end
  if mon.overlay.locked == nil then mon.overlay.locked = false end
  if mon.overlay.bg == nil then mon.overlay.bg = true end
  if mon.debug.enabled == nil then mon.debug.enabled = false end
  if mon.debug.w == nil then mon.debug.w = 380 end
  if mon.debug.h == nil then mon.debug.h = 280 end
  return mon
end

local function clamp(x, a, b) if x < a then return a elseif x > b then return b else return x end end
local function now() return (GetTime and GetTime()) or 0 end

local function copySorted(t)
  if not t then return {} end
  local c = {}
  for i=1,#t do c[i] = t[i] end
  table.sort(c)
  return c
end

local function percentile(t, p)
  if not t or #t == 0 then return 0 end
  local a = copySorted(t)
  local n = #a
  local k = math.max(1, math.min(n, math.floor((p/100) * n + 0.5)))
  return a[k]
end

-- Number formatters (reuse addon helpers when present)
local function short(n)
  if DFFT.shortNumberRounded then return DFFT.shortNumberRounded(n) end
  if DFFT.shortNumber        then return DFFT.shortNumber(n)        end
  -- minimal fallback
  if n >= 1e9 then return string.format("%.1fB", n/1e9)
  elseif n >= 1e6 then return string.format("%.1fM", n/1e6)
  elseif n >= 1e3 then return string.format("%.1fk", n/1e3)
  else return tostring(n) end
end

-- Keep in sync with AutoThresholds dynamic cap (display only)
local function dynCap(ilvl)
  ilvl = tonumber(ilvl) or 0
  if ilvl <= 0   then return 2.6 end
  if ilvl < 550  then return 2.4 + (ilvl/550)*0.2       -- 2.4..2.6
  elseif ilvl <= 650 then return 2.6 + ((ilvl-550)/100)*0.4 -- 2.6..3.0
  else
    local extra = math.max(0, math.min(1, (ilvl-650)/200)) * 0.6
    return 3.0 + extra                                  -- 3.0..3.6
  end
end

-- -----------------------------
-- Frames (overlay + debug)
-- -----------------------------
local overlay, oText
local debugF, dLines = nil, {}

local function ensureOverlay()
  if overlay then return end

  -- 🔒 Ensure subtables exist before any read/write
  local mon = ensureMonitorDB()

  overlay = CreateFrame("Frame", "DFFTThresholdOverlay", UIParent, "BackdropTemplate")
  overlay:SetSize(360, 34)
  overlay:SetPoint("TOPLEFT", UIParent, "TOPLEFT", mon.overlay.x or 160, mon.overlay.y or -140)
  overlay:SetMovable(true)
  overlay:EnableMouse(true)
  overlay:RegisterForDrag("LeftButton")
  overlay:SetScript("OnDragStart", function(self)
    if not (mon.overlay.locked) then self:StartMoving() end
  end)
  overlay:SetScript("OnDragStop", function(self)
    self:StopMovingOrSizing()
    local point, rel, relPoint, x, y = self:GetPoint(1)
    -- Re-ensure in case DB.monitor was wiped elsewhere
    local mon2 = ensureMonitorDB()
    mon2.overlay.x, mon2.overlay.y = x, y
  end)

  oText = overlay:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  oText:SetPoint("LEFT", overlay, "LEFT", 8, 0)
  oText:SetJustifyH("LEFT")

  local function applyStyle()
    local m = ensureMonitorDB() -- re-ensure if needed
    overlay:SetScale(m.overlay.scale or 1.0)
    overlay:SetAlpha(m.overlay.alpha or 1.0)
    if m.overlay.bg then
      overlay:SetBackdrop({
        bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 10, insets = { left=2, right=2, top=2, bottom=2 }
      })
      overlay:SetBackdropColor(0,0,0,0.55)
    else
      overlay:SetBackdrop(nil)
    end
  end
  overlay.ApplyStyle = applyStyle
  applyStyle()
  overlay:Hide()
end

local function ensureDebug()
  if debugF then return end
  local mon = ensureMonitorDB()

  debugF = CreateFrame("Frame", "DFFTThresholdDebug", UIParent, "BackdropTemplate")
  debugF:SetSize(mon.debug.w or 380, mon.debug.h or 280)
  debugF:SetPoint("CENTER")
  debugF:SetMovable(true)
  debugF:EnableMouse(true)
  debugF:RegisterForDrag("LeftButton")
  debugF:SetScript("OnDragStart", function(self) self:StartMoving() end)
  debugF:SetScript("OnDragStop",  function(self) self:StopMovingOrSizing() end)
  debugF:SetBackdrop({
    bgFile  = "Interface\\DialogFrame\\UI-DialogBox-Background",
    edgeFile= "Interface\\DialogFrame\\UI-DialogBox-Border",
    edgeSize= 24, insets = { left=8, right=8, top=8, bottom=8 }
  })
  debugF:SetBackdropColor(0,0,0,0.8)

  local title = debugF:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
  title:SetPoint("TOPLEFT", 16, -12)
  title:SetText("DamageFilterFT — Threshold Debug")

  local close = CreateFrame("Button", nil, debugF, "UIPanelCloseButton")
  close:SetPoint("TOPRIGHT", 2, 2)

  local labels = {
    "Role", "ilvl", "Samples (all/crits/target-crits)", "Last recalculated",
    "Small ≤ (p40)", "Big ≥ (p75)", "Ultra ≥ (cap×, p95 crits on target)",
    "p40 (all)", "p75 (all)", "p95 (crits)", "p95 (crits on target)"
  }
  local y = -44
  for i, txt in ipairs(labels) do
    local l = debugF:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    l:SetPoint("TOPLEFT", 16, y)
    l:SetText(txt .. ":")
    local v = debugF:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    v:SetPoint("LEFT", l, "RIGHT", 8, 0)
    dLines[i] = v
    y = y - 20
  end

  -- Buttons
  local btnRecal = CreateFrame("Button", nil, debugF, "UIPanelButtonTemplate")
  btnRecal:SetSize(120, 22)
  btnRecal:SetPoint("BOTTOMLEFT", 16, 14)
  btnRecal:SetText("Recalibrate")
  btnRecal:SetScript("OnClick", function()
    if DFFT.Auto_RecalibrateNow then DFFT.Auto_RecalibrateNow() end
  end)

  local btnReset = CreateFrame("Button", nil, debugF, "UIPanelButtonTemplate")
  btnReset:SetSize(160, 22)
  btnReset:SetPoint("LEFT", btnRecal, "RIGHT", 8, 0)
  btnReset:SetText("Clear samples")
  btnReset:SetScript("OnClick", function()
    local A = DFFT.Auto or {}
    A.recent_all, A.recent_crit, A.recent_crit_tg = {}, {}, {}
    if DFFT.Auto_InitSeed then DFFT.Auto_InitSeed() end
    M.RequestRefresh()
  end)

  debugF:Hide()
end

-- -----------------------------
-- Refresh logic (throttled)
-- -----------------------------
M._throttle = 0.25
M._pending  = false

function M.RefreshNow()
  M._pending = false
  ensureOverlay() -- may create overlay/oText the first time

  local A   = DFFT.Auto or {}
  local all = A.recent_all     or {}
  local cr  = A.recent_crit    or {}
  local tgt = A.recent_crit_tg or {}
  local role = A.role or "?"
  local ilvl = tonumber(A.ilvl or 0) or 0
  local cap  = dynCap(ilvl)

  local smallMax = DB.smallMax    or 0
  local bigMin   = DB.bigMin      or 0
  local ultraMin = DB.veryBigMin  or 0

  if overlay and overlay:IsShown() and oText then
    local line = string.format(
      "S ≤ %s   |   B ≥ %s   |   U ≥ %s   (cap×%.2f, ilvl %d, %d/%d crits)",
      short(smallMax), short(bigMin), short(ultraMin),
      cap, ilvl, (#tgt > 0 and #tgt or #cr), #all
    )
    oText:SetText(line)
  end

  if debugF and debugF:IsShown() then
    local p40 = percentile(all, 40)
    local p75 = percentile(all, 75)
    local c95 = percentile(cr, 95)
    local t95 = percentile(tgt, 95)

    local last = A.lastRecalc or 0
    local ago  = last > 0 and (now() - last) or -1
    local when = (ago >= 0) and (string.format("%.1fs ago", ago)) or "n/a"

    local lines = {
      tostring(role), tostring(ilvl),
      string.format("%d / %d / %d", #all, #cr, #tgt),
      when,
      string.format("%s (p40=%s)", short(smallMax), short(p40)),
      string.format("%s (p75=%s)", short(bigMin), short(p75)),
      string.format("%s (cap×%.2f ; p95tg=%s)", short(ultraMin), cap, short(t95)),
      short(p40),
      short(p75),
      short(c95),
      short(t95),
    }
    for i=1,#dLines do dLines[i]:SetText(lines[i] or "") end
  end
end

function M.RequestRefresh()
  if M._pending then return end
  M._pending = true
  C_Timer.After(M._throttle, M.RefreshNow)
end

-- -----------------------------
-- Wrappers on auto functions (no changes required elsewhere)
-- -----------------------------
local function wrap(name)
  local orig = DFFT[name]
  if type(orig) ~= "function" then return end
  DFFT[name] = function(...)
    local r = orig(...)
    M.RequestRefresh()
    return r
  end
end

-- Try to wrap immediately; also retry at PLAYER_LOGIN (in case load order differs)
wrap("Auto_OnNewAmount")
wrap("Auto_RecalibrateNow")
wrap("Auto_UpdatePlayer")

local f = CreateFrame("Frame")
f:RegisterEvent("PLAYER_LOGIN")
f:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
f:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:SetScript("OnEvent", function(_, evt)
  if evt == "PLAYER_LOGIN" then
    -- Late wrap (if functions were defined after this file)
    wrap("Auto_OnNewAmount")
    wrap("Auto_RecalibrateNow")
    wrap("Auto_UpdatePlayer")

    ensureOverlay()
    ensureDebug()

    local mon = ensureMonitorDB()
    -- Explicitly show/hide on login based on saved flags
    if mon.overlay.enabled then overlay:Show() else overlay:Hide() end
    if mon.debug.enabled   then debugF:Show()   else debugF:Hide()   end

    M.RequestRefresh()
  else
    -- equipment/spec/world changes -> refresh soon
    M.RequestRefresh()
  end
end)

-- -----------------------------
-- Public toggles + slash
-- -----------------------------
function DFFT.toggleMonitor()
  ensureOverlay()
  local mon = ensureMonitorDB()
  if overlay:IsShown() then
    overlay:Hide()
    mon.overlay.enabled = false
  else
    overlay:Show()
    mon.overlay.enabled = true
    M.RequestRefresh()
  end
end

function DFFT.toggleDebug()
  ensureDebug()
  local mon = ensureMonitorDB()
  if debugF:IsShown() then
    debugF:Hide()
    mon.debug.enabled = false
  else
    debugF:Show()
    mon.debug.enabled = true
    M.RequestRefresh()
  end
end

-- Extra convenience options (scale/alpha/lock/bg)
function DFFT.monitorSetScale(s)
  local mon = ensureMonitorDB()
  mon.overlay.scale = clamp(tonumber(s) or 1.0, 0.5, 2.0)
  ensureOverlay(); overlay:ApplyStyle(); M.RequestRefresh()
end

function DFFT.monitorSetAlpha(a)
  local mon = ensureMonitorDB()
  mon.overlay.alpha = clamp(tonumber(a) or 1.0, 0.2, 1.0)
  ensureOverlay(); overlay:ApplyStyle(); M.RequestRefresh()
end

function DFFT.monitorToggleLock()
  local mon = ensureMonitorDB()
  mon.overlay.locked = not mon.overlay.locked
  print(("DFFT Monitor: %s"):format(mon.overlay.locked and "locked" or "unlocked"))
end

function DFFT.monitorToggleBG()
  local mon = ensureMonitorDB()
  mon.overlay.bg = not mon.overlay.bg
  ensureOverlay(); overlay:ApplyStyle()
end

-- Slash (separate from your main Slash.lua)
SLASH_DFFTMON1 = "/dfftm"
SlashCmdList.DFFTMON = function(msg)
  msg = (msg or ""):lower()
  if msg == "scale" then
    print("Usage: /dfftm scale <0.5..2.0>")
  elseif msg:find("^scale%s") then
    local _, _, s = msg:find("^scale%s+([%d%.]+)")
    return DFFT.monitorSetScale(s)
  elseif msg == "alpha" then
    print("Usage: /dfftm alpha <0.2..1.0>")
  elseif msg:find("^alpha%s") then
    local _, _, a = msg:find("^alpha%s+([%d%.]+)")
    return DFFT.monitorSetAlpha(a)
  elseif msg == "lock" then
    return DFFT.monitorToggleLock()
  elseif msg == "bg" then
    return DFFT.monitorToggleBG()
  elseif msg == "debug" then
    return DFFT.toggleDebug()
  else
    return DFFT.toggleMonitor()
  end
end
